#include "fs.h"

size_t fs_filesz(int fd);
extern void ramdisk_read(void* buf, off_t offset, size_t len);
extern void ramdisk_write(const void* buf, off_t offset, size_t len);
extern void dispinfo_read(void* buf, off_t offset, size_t len);
extern size_t events_read(void* buf, size_t len);
extern void fb_write(const void* buf, off_t offset, size_t len);

typedef struct {
  char *name;
  size_t size;
  off_t disk_offset;
  off_t open_offset;
} Finfo;

enum {FD_STDIN, FD_STDOUT, FD_STDERR, FD_FB, FD_EVENTS, FD_DISPINFO, FD_NORMAL};

/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
  {"stdin (note that this is not the actual stdin)", 0, 0},
  {"stdout (note that this is not the actual stdout)", 0, 0},
  {"stderr (note that this is not the actual stderr)", 0, 0},
  [FD_FB] = {"/dev/fb", 0, 0},
  [FD_EVENTS] = {"/dev/events", 0, 0},
  [FD_DISPINFO] = {"/proc/dispinfo", 128, 0},
#include "files.h"
};

#define NR_FILES (sizeof(file_table) / sizeof(file_table[0]))

void init_fs() {
  // TODO: initialize the size of /dev/fb
  file_table[FD_FB].open_offset = 0;
  file_table[FD_FB].size = _screen.height * _screen.width * sizeof(uint32_t);
}

int fs_open(const char* pathname, int flags, int mode) {
  int fd;
  for (fd = 0; fd < NR_FILES; fd++)
    if (strcmp(pathname, file_table[fd].name) == 0)
      break;
  assert(fd < NR_FILES);
  file_table[fd].open_offset = 0;
  return fd;
}

ssize_t fs_read(int fd, void *buf, size_t len) {
  // 获取文件大小和对应的文件信息结构体
  ssize_t fs_size = fs_filesz(fd);
  Finfo* fp = &file_table[fd];
  
  switch(fd) {
      case FD_STDOUT: // 标准输出不可读，直接返回0
          len = 0; // 确保返回0
          break;

      case FD_EVENTS: // 事件
          // 调用事件读取函数，返回实际读取长度
          len = events_read(buf, len);
          break;

      case FD_DISPINFO: // 显示信息
          // 检查偏移是否越界
          if (fp->open_offset >= fp->size)
              return 0;
          // 调整读取长度以避免越界
          if (fp->open_offset + len > fp->size)
              len = fp->size - fp->open_offset;
          // 从显示信息中读取数据并更新偏移
          dispinfo_read(buf, fp->open_offset, len);
          fp->open_offset += len;    
          break;

      default: // 普通文件或设备
          // 检查偏移越界或零长度请求
          if (fp->open_offset >= fs_size || len == 0)
              return 0;
          // 调整读取长度以避免越界
          if (fp->open_offset + len > fs_size)
              len = fs_size - fp->open_offset;
          // 从磁盘读取数据并更新偏移
          ramdisk_read(buf, fp->disk_offset + fp->open_offset, len);
          fp->open_offset += len;
          break;
  }
  return len; // 返回实际读取的字节数
}

ssize_t fs_write(int fd, const void *buf, size_t len) {
  ssize_t fs_size = fs_filesz(fd);
  Finfo* fp = &file_table[fd];
  
  switch(fd) {
      case FD_STDOUT:  // 标准输出
      case FD_STDERR:  // 标准错误
          // 逐字符输出到控制台
          for(int i = 0; i < len; i++) {
              _putc(((char*)buf)[i]);
          }
          break;

      case FD_FB:      // 帧缓冲设备
          // 写入帧缓冲并更新偏移
          fb_write(buf, fp->open_offset, len);
          fp->open_offset += len;
          break;

      default:         // 普通文件或设备
          // 检查偏移是否越界
          if (fp->open_offset >= fs_size)
              return 0;
          // 调整写入长度以避免越界
          if (fp->open_offset + len > fs_size)
              len = fs_size - fp->open_offset;
          // 写入磁盘并更新偏移
          ramdisk_write(buf, fp->disk_offset + fp->open_offset, len);
          fp->open_offset += len;
          break;
  }
  return len; // 返回实际写入的字节数
}


off_t fs_lseek(int fd, off_t offset, int whence) {
  off_t result = -1;
  ssize_t fs_size = fs_filesz(fd);
  Finfo* fp = &file_table[fd];
  
  switch(whence) {
      case SEEK_SET: // 从文件开始计算偏移
          if (offset >= 0 && offset <= fs_size) {
              fp->open_offset = offset;
              result = fp->open_offset;
          }
          break;

      case SEEK_CUR: // 从当前位置计算偏移
          if ((offset + fp->open_offset >= 0) && 
              (offset + fp->open_offset <= fs_size)) {
              fp->open_offset += offset;
              result = fp->open_offset;
          }
          break;

      case SEEK_END: // 从文件末尾计算偏移
          // 允许偏移超过当前文件大小
          fp->open_offset = fs_size + offset;
          result = fp->open_offset;
          break;
  }
  
  return result; // 返回新偏移或-1
}

int fs_close(int fd) {
	return 0;
}

size_t fs_filesz(int fd) {
  if (fd < 0 || fd >= NR_FILES) {
    return -1;
  }
  return file_table[fd].size;
}

// /*
// ssize_t read(int fd, void *buf, size_t count);

// DESCRIPTION
//        read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

//        On  files  that  support seeking, the read operation commences at the current file offset, and the file offset is incremented by the number of bytes read.  If the current file offset is at or
//        past the end of file, no bytes are read, and read() returns zero.

//        If count is zero, read() may detect the errors described below.  In the absence of any errors, or if read() does not check for errors, a read() with a count of 0 returns zero and has no other
//        effects.

//        If count is greater than SSIZE_MAX, the result is unspecified.

// RETURN VALUE
//        On  success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.  It is not an error if this number is smaller than the number
//        of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading  from  a  pipe,  or
//        from a terminal), or because read() was interrupted by a signal.  See also NOTES.

//        On error, -1 is returned, and errno is set appropriately.  In this case, it is left unspecified whether the file position (if any) changes.
// */

// ssize_t fs_read(int fd, void* buf, size_t len) {
//   if (fd < 0 || fd >= NR_FILES) {
//       return -1;
//   }

//   Finfo* fp = &file_table[fd];
//   ssize_t filesz = fs_filesz(fd);

//   // 当前偏移已超过文件大小，直接返回0
//   if (fp->open_offset >= filesz) {
//       return 0;
//   }

//   // 限制读取长度不超过filesz
//   size_t effective_len = len > filesz ? filesz : len;
//   ssize_t available = filesz - fp->open_offset;
//   ssize_t nread = effective_len < (size_t)available ? effective_len : available;

//   Log("nread:%d", nread);

//   switch (fd) {
//       case FD_STDIN:
//       case FD_STDOUT:
//       case FD_STDERR:
//       case FD_FB:
//           return -1;
//       case FD_DISPINFO:
//           dispinfo_read(buf, fp->open_offset, nread);
//           break;
//       default:
//           ramdisk_read(buf, fp->disk_offset + fp->open_offset, nread);
//           break;
//   }

//   fp->open_offset += nread;
//   Log("open_offset:%d\n", fp->open_offset);
//   return nread;
// }

// /*
// ssize_t write(int fd, const void *buf, size_t count);

// DESCRIPTION
//        write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.

//        The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setr‐
//        limit(2)), or the call was interrupted by a signal handler after having written less than count bytes.  (See also pipe(7).)

//        For a seekable file (i.e., one to which lseek(2) may be applied, for example, a regular file) writing takes place at the current file offset, and the file offset is incremented by the  number
//        of bytes actually written.  If the file was open(2)ed with O_APPEND, the file offset is first set to the end of the file before writing.  The adjustment of the file offset and the write oper‐
//        ation are performed as an atomic step.

//        POSIX requires that a read(2) which can be proved to occur after a write() has returned returns the new data.  Note that not all filesystems are POSIX conforming.

// RETURN VALUE
//        On success, the number of bytes written is returned (zero indicates nothing was written).  It is not an error if this number is smaller than the number of bytes requested; this may happen for
//        example because the disk device was filled.  See also NOTES.

//        On error, -1 is returned, and errno is set appropriately.

//        If  count  is zero and fd refers to a regular file, then write() may return a failure status if one of the errors below is detected.  If no errors are detected, or error detection is not per‐
//        formed, 0 will be returned without causing any other effect.  If count is zero and fd refers to a file other than a regular file, the results are not specified.
// */
// ssize_t fs_write(int fd, const void* buf, size_t len) {
//   if (fd < 0 || fd >= NR_FILES) {
//       return -1;
//   }

//   Finfo* fp = &file_table[fd];
//   ssize_t filesz = fs_filesz(fd);

//   // 限制读取长度不超过filesz
//   size_t effective_len = len > filesz ? filesz : len;
//   ssize_t available = filesz - fp->open_offset;
//   ssize_t nwrite = effective_len < (size_t)available ? effective_len : available;

//   Log("nwrite:%d", nwrite);

//   switch (fd) {
//       case FD_STDOUT:
//       case FD_STDERR:
//           for (int i = 0; i < nwrite; i++) {
//               _putc(((char*)buf)[i]);
//           }
//           break;
//       case FD_FB:
//           fb_write(buf, fp->open_offset, len);
//           fp->open_offset += len;
//           break;
//       case FD_STDIN:
//       case FD_DISPINFO:
//           return -1;
//       default:
//           ramdisk_write(buf, fp->disk_offset + fp->open_offset, nwrite);
//           break;
//   }

//   // 更新文件偏移
//   fp->open_offset += nwrite;
//   Log("open_offset:%d", fp->open_offset);
//   return nwrite;
// }

// /*
// off_t lseek(int fd, off_t offset, int whence);

// DESCRIPTION
//        The lseek() function repositions the offset of the open file associated with the file descriptor fd to the argument offset according to the directive whence as follows:

//        SEEK_SET
//               The offset is set to offset bytes.

//        SEEK_CUR
//               The offset is set to its current location plus offset bytes.

//        SEEK_END
//               The offset is set to the size of the file plus offset bytes.

//        The  lseek() function allows the file offset to be set beyond the end of the file (but this does not change the size of the file).  If data is later written at this point, subsequent reads of
//        the data in the gap (a "hole") return null bytes ('\0') until data is actually written into the gap.

//    Seeking file data and holes
//        Since version 3.1, Linux supports the following additional values for whence:

//        SEEK_DATA
//               Adjust the file offset to the next location in the file greater than or equal to offset containing data.  If offset points to data, then the file offset is set to offset.

//        SEEK_HOLE
//               Adjust the file offset to the next hole in the file greater than or equal to offset.  If offset points into the middle of a hole, then the file offset is set to offset.  If there is no
//               hole past offset, then the file offset is adjusted to the end of the file (i.e., there is an implicit hole at the end of any file).

//        In both of the above cases, lseek() fails if offset points past the end of the file.

//        These  operations  allow applications to map holes in a sparsely allocated file.  This can be useful for applications such as file backup tools, which can save space when creating backups and
//        preserve holes, if they have a mechanism for discovering holes.

//        For the purposes of these operations, a hole is a sequence of zeros that (normally) has not been allocated in the underlying file storage.  However, a filesystem  is  not  obliged  to  report
//        holes,  so these operations are not a guaranteed mechanism for mapping the storage space actually allocated to a file.  (Furthermore, a sequence of zeros that actually has been written to the
//        underlying storage may not be reported as a hole.)  In the simplest implementation, a filesystem can support the operations by making SEEK_HOLE always return the offset  of  the  end  of  the
//        file, and making SEEK_DATA always return offset (i.e., even if the location referred to by offset is a hole, it can be considered to consist of data that is a sequence of zeros).

//        The _GNU_SOURCE feature test macro must be defined in order to obtain the definitions of SEEK_DATA and SEEK_HOLE from <unistd.h>.

//        The SEEK_HOLE and SEEK_DATA operations are supported for the following filesystems:

//        *  Btrfs (since Linux 3.1)

//        *  OCFS (since Linux 3.2)

//        *  XFS (since Linux 3.5)

//        *  ext4 (since Linux 3.8)

//        *  tmpfs (since Linux 3.8)

// RETURN VALUE
//        Upon  successful completion, lseek() returns the resulting offset location as measured in bytes from the beginning of the file.  On error, the value (off_t) -1 is returned and errno is set to
//        indicate the error.
// */
// off_t fs_lseek(int fd, off_t offset, int whence) {
//   if (fd < 0 || fd >= NR_FILES) {
//       return -1;
//   }

//   switch (fd) {
//       case FD_STDIN:
//       case FD_STDOUT:
//       case FD_STDERR:
//           return -1;
//       default:
//           break;
//   }

//   Finfo* fp = &file_table[fd];
//   off_t new_offset;
//   ssize_t file_size = fs_filesz(fd);

//   switch (whence) {
//       case SEEK_SET:
//           new_offset = offset;
//           break;
//       case SEEK_CUR:
//           new_offset = fp->open_offset + offset;
//           break;
//       case SEEK_END:
//           new_offset = file_size + offset;
//           break;
//       default:
//           return -1;
//   }

//   // 确保新偏移非负
//   if (new_offset < 0) {
//       return -1;
//   }

//   // 确保偏移不超出文件末尾
//   fp->open_offset = new_offset <= file_size ? new_offset : file_size;
//   return fp->open_offset;
// }



